﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.DataGrid
{
  partial class DataGridControl
  {
    #region CellEditorDisplayConditions Property

    public static readonly DependencyProperty CellEditorDisplayConditionsProperty = DependencyProperty.Register(
      "CellEditorDisplayConditions",
      typeof( CellEditorDisplayConditions ),
      typeof( DataGridControl ),
      new PropertyMetadata(
        CellEditorDisplayConditions.None,
        new PropertyChangedCallback( DataGridControl.OnCellEditorDisplayConditionsChanged ) ) );

    public CellEditorDisplayConditions CellEditorDisplayConditions
    {
      get
      {
        return ( CellEditorDisplayConditions )this.GetValue( DataGridControl.CellEditorDisplayConditionsProperty );
      }
      set
      {
        this.SetValue( DataGridControl.CellEditorDisplayConditionsProperty, value );
      }
    }

    private static void OnCellEditorDisplayConditionsChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      var dataGrid = sender as DataGridControl;
      if( dataGrid == null )
        return;

      var inspector = CellEditorVisibilityInspector.Create( dataGrid ).Check();

      dataGrid.UpdateCellsEditorVisibility( inspector );
    }

    #endregion

    #region UpdateSourceTrigger Property

    public static readonly DependencyProperty UpdateSourceTriggerProperty = DependencyProperty.Register(
      "UpdateSourceTrigger",
      typeof( UpdateSourceTrigger ),
      typeof( DataGridControl ),
      new PropertyMetadata( UpdateSourceTrigger.RowEndEdit ) );

    public UpdateSourceTrigger UpdateSourceTrigger
    {
      get
      {
        return ( UpdateSourceTrigger )this.GetValue( DataGridControl.UpdateSourceTriggerProperty );
      }
      set
      {
        this.SetValue( DataGridControl.UpdateSourceTriggerProperty, value );
      }
    }

    #endregion

    #region EditTriggers Property

    public static readonly DependencyProperty EditTriggersProperty = DependencyProperty.Register(
      "EditTriggers",
      typeof( EditTriggers ),
      typeof( DataGridControl ),
      new PropertyMetadata(
        EditTriggers.ClickOnCurrentCell,
        new PropertyChangedCallback( DataGridControl.OnEditTriggersChanged ) ) );

    public EditTriggers EditTriggers
    {
      get
      {
        return ( EditTriggers )this.GetValue( DataGridControl.EditTriggersProperty );
      }
      set
      {
        this.SetValue( DataGridControl.EditTriggersProperty, value );
      }
    }

    private static void OnEditTriggersChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      var dataGrid = ( DataGridControl )sender;

      dataGrid.CheckEditTriggers();
    }

    #endregion

    #region ValidationErrors Property

    public IEnumerable<ValidationErrorBase> ValidationErrors
    {
      get
      {
        return m_dataGridViewModel.ValidationErrors;
      }
    }

    #endregion

    #region HasValidationErrors Property

    public bool HasValidationErrors
    {
      get
      {
        return m_dataGridViewModel.HasValidationErrors;
      }
    }

    #endregion

    #region HasRestrictiveValidationErrors Property

    public bool HasRestrictiveValidationErrors
    {
      get
      {
        return m_dataGridViewModel.HasRestrictiveValidationErrors;
      }
    }

    #endregion

    #region EditBeginning Event

    public event EventHandler<DataGridEditCancelEventArgs> EditBeginning;

    private void RaiseEditBeginning( DataGridEditCancelEventArgs e )
    {
      if( this.EditBeginning != null )
      {
        this.EditBeginning( this, e );
      }
    }

    #endregion

    #region EditBegun Event

    public event EventHandler<DataGridEditEventArgs> EditBegun;

    private void RaiseEditBegun( DataGridEditEventArgs e )
    {
      if( this.EditBegun != null )
      {
        this.EditBegun( this, e );
      }
    }

    #endregion

    #region EditCanceling Event

    public event EventHandler<DataGridEditEventArgs> EditCanceling;

    private void RaiseEditCanceling( DataGridEditEventArgs e )
    {
      if( this.EditCanceling != null )
      {
        this.EditCanceling( this, e );
      }
    }

    #endregion

    #region EditCanceled Event

    public event EventHandler<DataGridEditEventArgs> EditCanceled;

    private void RaiseEditCanceled( DataGridEditEventArgs e )
    {
      if( this.EditCanceled != null )
      {
        this.EditCanceled( this, e );
      }
    }

    #endregion

    #region EditEnding Event

    public event EventHandler<DataGridEditCancelEventArgs> EditEnding;

    private void RaiseEditEnding( DataGridEditCancelEventArgs e )
    {
      if( this.EditEnding != null )
      {
        this.EditEnding( this, e );
      }
    }

    #endregion

    #region EditEnded Event

    public event EventHandler<DataGridEditEventArgs> EditEnded;

    private void RaiseEditEnded( DataGridEditEventArgs e )
    {
      if( this.EditEnded != null )
      {
        this.EditEnded( this, e );
      }
    }

    #endregion

    #region SourceItemEditBeginning Event

    public event EventHandler<SourceItemHandledEventArgs> SourceItemEditBeginning;

    private void RaiseSourceItemEditBeginning( SourceItemHandledEventArgs e )
    {
      if( this.SourceItemEditBeginning != null )
      {
        this.SourceItemEditBeginning( this, e );
      }
    }

    #endregion

    #region SourceItemEditBegun Event

    public event EventHandler<SourceItemEventArgs> SourceItemEditBegun;

    private void RaiseSourceItemEditBegun( SourceItemEventArgs e )
    {
      if( this.SourceItemEditBegun != null )
      {
        this.SourceItemEditBegun( this, e );
      }
    }

    #endregion

    #region SourceItemEditCanceling Event

    public event EventHandler<SourceItemHandledEventArgs> SourceItemEditCanceling;

    private void RaiseSourceItemEditCanceling( SourceItemHandledEventArgs e )
    {
      if( this.SourceItemEditCanceling != null )
      {
        this.SourceItemEditCanceling( this, e );
      }
    }

    #endregion

    #region SourceItemEditCanceled Event

    public event EventHandler<SourceItemEventArgs> SourceItemEditCanceled;

    private void RaiseSourceItemEditCanceled( SourceItemEventArgs e )
    {
      if( this.SourceItemEditCanceled != null )
      {
        this.SourceItemEditCanceled( this, e );
      }
    }

    #endregion

    #region SourceItemEditEnding Event

    public event EventHandler<SourceItemHandledEventArgs> SourceItemEditEnding;

    private void RaiseSourceItemEditEnding( SourceItemHandledEventArgs e )
    {
      if( this.SourceItemEditEnding != null )
      {
        this.SourceItemEditEnding( this, e );
      }
    }

    #endregion

    #region SourceItemEditEnded Event

    public event EventHandler<SourceItemEditEndedEventArgs> SourceItemEditEnded;

    private void RaiseSourceItemEditEnded( SourceItemEditEndedEventArgs e )
    {
      if( this.SourceItemEditEnded != null )
      {
        this.SourceItemEditEnded( this, e );
      }
    }

    #endregion

    #region CreatingNewSourceItem Event

    public event EventHandler<CreatingNewSourceItemEventArgs> CreatingNewSourceItem;

    private void RaiseCreatingNewSourceItem( CreatingNewSourceItemEventArgs e )
    {
      if( this.CreatingNewSourceItem != null )
      {
        this.CreatingNewSourceItem( this, e );
      }
    }

    #endregion

    #region NewSourceItemCreated Event

    public event EventHandler<SourceItemEventArgs> NewSourceItemCreated;

    private void RaiseNewSourceItemCreated( SourceItemEventArgs e )
    {
      if( this.NewSourceItemCreated != null )
      {
        this.NewSourceItemCreated( this, e );
      }
    }

    #endregion

    #region Edit Event Handlers Methods

    private void OnRequestBeginEdit( object sender, XceedRoutedEventArgs args )
    {
      XceedRoutedEventRequestEditEventArgs eventArgs = args as XceedRoutedEventRequestEditEventArgs;
      if( ( eventArgs == null ) || ( eventArgs.Handled ) )
        return;

      var source = eventArgs.OriginalSource;
      var targetRow = source as EditableRow;
      var targetCell = source as EditableCell;

      if( targetRow != null )
      {
        eventArgs.Handled = true;
        eventArgs.SetResult( m_editingManager.BeginEdit( targetRow ) );
      }
      else if( targetCell != null )
      {
        eventArgs.Handled = true;
        eventArgs.SetResult( m_editingManager.BeginEdit( targetCell ) );
      }
    }

    private void OnRequestEndEdit( object sender, XceedRoutedEventArgs args )
    {
      XceedRoutedEventRequestEditEventArgs eventArgs = args as XceedRoutedEventRequestEditEventArgs;
      if( ( eventArgs == null ) || ( eventArgs.Handled ) )
        return;

      var source = eventArgs.OriginalSource;
      var targetRow = source as EditableRow;
      var targetCell = source as EditableCell;

      if( targetRow != null )
      {
        eventArgs.Handled = true;
        eventArgs.SetResult( m_editingManager.EndEdit( targetRow ) );
      }
      else if( targetCell != null )
      {
        eventArgs.Handled = true;
        eventArgs.SetResult( m_editingManager.EndEdit( targetCell ) );
      }
    }

    private void OnRequestCancelEdit( object sender, XceedRoutedEventArgs args )
    {
      XceedRoutedEventRequestEditEventArgs eventArgs = args as XceedRoutedEventRequestEditEventArgs;
      if( ( eventArgs == null ) || ( eventArgs.Handled ) )
        return;

      var source = eventArgs.OriginalSource;
      var targetRow = source as EditableRow;
      var targetCell = source as EditableCell;

      if( targetRow != null )
      {
        eventArgs.Handled = true;
        eventArgs.SetResult( m_editingManager.CancelEdit( targetRow ) );
      }
      else if( targetCell != null )
      {
        eventArgs.Handled = true;
        eventArgs.SetResult( m_editingManager.CancelEdit( targetCell ) );
      }
    }

    #endregion

    #region Editing Public Methods

    public void BeginEdit()
    {
      var current = this.DataGridContext.CurrentPath;
      if( current == null )
        return;

      this.BeginEdit( current );
    }

    public void BeginEdit( object item )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      if( this.DataSource.IsPrimaryKeyVolatile )
        throw new InvalidOperationException( "Unable to retrieve the path from the specified item. Either the grid is not bound to a data source or the primary keys have changed." );

      DataPath dataPath = this.DataGridContext.GetDataPathFromItem( item );
      if( dataPath == null )
        throw new InvalidOperationException( "An attempt was made to start the edit process on an item that is not part of the grid." );

      EditableRowViewModel viewModel = m_dataGridViewModel.GetRow( dataPath, item, false );

      try
      {
        m_editingManager.BeginEdit( viewModel );
      }
      catch( Exception ex )
      {
        throw new DataGridException( "An error occurred while trying to start the edit process.", ex );
      }
    }

    public bool EndEdit()
    {
      bool success = true;
      var editedRows = m_dataGridViewModel.EditedRows.ToList();

      foreach( var targetRow in editedRows )
      {
        try
        {
          if( !m_editingManager.EndEdit( targetRow ) )
          {
            success = false;
          }
        }
        catch( Exception ex )
        {
          throw new DataGridException( "An error occurred while ending the edit process.", ex );
        }
      }

      return success;
    }

    public void CancelEdit()
    {
      var editedRows = m_dataGridViewModel.EditedRows.ToList();

      try
      {
        foreach( var targetRow in editedRows )
        {
          m_editingManager.CancelEdit( targetRow );
        }
      }
      catch( Exception ex )
      {
        throw new DataGridException( "An error occurred while canceling the edit process.", ex );
      }
    }

    private void BeginEdit( DataGridPath dataPath )
    {
      if( dataPath == null )
        throw new ArgumentNullException( "dataPath" );

      var container = this.GetContainerFromPath( dataPath ) as EditableRow;
      if( container == null )
        return;

      m_editingManager.BeginEdit( container );
    }

    #endregion

    private void OnRequestPropertyChangedCommit( object sender, XceedRoutedEventArgs args )
    {
      EditableCell cell = args.OriginalSource as EditableCell;
      if( cell == null )
        return;

      m_editingManager.BeginEditAndCommitValue( cell );
    }

    private void OnActivatingCellEditor( object sender, XceedRoutedEventArgs args )
    {
      var eventArgs = ( XceedRoutedEventActivatingCellEditorEventArgs )args;
      var targetCell = ( Cell )eventArgs.OriginalSource;
      var targetColumn = targetCell.ParentColumn;

      targetColumn.OnActivatingCellEditor( targetCell, eventArgs.Editor );
    }

    private void UpdateCellsEditorVisibility( CellEditorVisibilityInspector inspector )
    {
      if( !inspector.NeedsUpdate )
        return;

      this.RefreshColumns( true );
      this.RefreshData( true );
    }

    private void CheckEditTriggers()
    {
      var currentRow = this.CurrentElement as EditableRow;
      if( currentRow == null )
        return;

      this.CheckEditTriggers( currentRow, this.CurrentCell as EditableCell, false );
    }

    private void CheckEditTriggers( EditableRow targetRow, EditableCell targetCell, bool wasClicked )
    {
      EditableRowViewModel rowViewModel = ( targetRow != null ) ? targetRow.ViewModel : null;
      EditableCellViewModel cellViewModel = ( targetCell != null ) ? targetCell.ViewModel : null;

      if( cellViewModel != null )
      {
        if( this.StartEditing( targetCell, wasClicked ) )
        {
          if( !cellViewModel.IsBeingEdited )
          {
            m_editingManager.BeginEdit( targetCell );
          }

          return;
        }
      }

      if( rowViewModel != null )
      {
        if( this.StartEditing( targetRow ) )
        {
          if( ( cellViewModel != null ) && ( !cellViewModel.IsBeingEdited ) )
          {
            m_editingManager.BeginEdit( targetCell );
          }
          else if( !rowViewModel.IsBeingEdited )
          {
            m_editingManager.BeginEdit( targetRow );
          }

          return;
        }
      }
    }

    private bool StartEditing( EditableRow targetRow )
    {
      return ( this.Match( this.EditTriggers, Xceed.Silverlight.DataGrid.EditTriggers.RowIsCurrent ) )
          && ( this.IsCurrentRow( targetRow ) );
    }

    private bool StartEditing( EditableCell targetCell, bool wasClicked )
    {
      var editTriggers = this.GetInheritedEditTriggers( targetCell );

      bool isCurrentCell = this.IsCurrentCell( targetCell );
      bool isCurrentRow = this.IsCurrentRow( targetCell );
      bool startEditing = false;


      startEditing |= this.Match( editTriggers, Xceed.Silverlight.DataGrid.EditTriggers.CellIsCurrent )
                   && isCurrentCell;

      startEditing |= this.Match( editTriggers, Xceed.Silverlight.DataGrid.EditTriggers.ClickOnCurrentCell )
                   && isCurrentCell
                   && wasClicked;

      startEditing |= this.Match( editTriggers, Xceed.Silverlight.DataGrid.EditTriggers.RowIsCurrent )
                   && isCurrentRow;

      return startEditing;
    }

    private bool ShowEditor( EditableCell targetCell )
    {
      if( ( targetCell == null )
        || ( targetCell.ViewModel == null )
        || ( !m_editingManager.CanBeEdited( targetCell.ViewModel ) ) )
        return false;

      var conditions = this.GetInheritedCellEditorDisplayConditions( targetCell );
      bool showEditor = false;

      showEditor |= this.DisplayEditorAlways( targetCell, conditions );
      showEditor |= this.DisplayEditorIfCellIsCurrent( targetCell, conditions );
      showEditor |= this.DisplayEditorIfRowIsCurrent( targetCell, conditions );
      showEditor |= this.DisplayEditorIfRowIsBeingEdited( targetCell, conditions );
      showEditor |= this.DisplayEditorIfMouseIsOverCell( targetCell, conditions );
      showEditor |= this.DisplayEditorIfMouseIsOverRow( targetCell, conditions );

      if( !showEditor && this.IsCurrentCell( targetCell ) )
      {
        var viewModel = targetCell.ViewModel;

        showEditor = ( viewModel != null )
                  && ( viewModel.IsBeingEdited );
      }

      return showEditor;
    }

    private CellEditorDisplayConditions GetInheritedCellEditorDisplayConditions( EditableCell targetCell )
    {
      if( targetCell != null )
      {
        var column = targetCell.ParentColumn;
        if( column != null )
        {
          var conditions = column.CellEditorDisplayConditions;
          if( conditions.HasValue )
            return conditions.Value;
        }
      }

      return this.CellEditorDisplayConditions;
    }

    private EditTriggers GetInheritedEditTriggers( EditableCell targetCell )
    {
      if( targetCell != null )
      {
        var column = targetCell.ParentColumn;
        if( column != null )
        {
          var conditions = column.EditTriggers;
          if( conditions.HasValue )
            return conditions.Value;
        }
      }

      return this.EditTriggers;
    }

    private bool DisplayEditorAlways( EditableCell targetCell, CellEditorDisplayConditions conditions )
    {
      return this.Match( conditions, CellEditorDisplayConditions.Always );
    }

    private bool DisplayEditorIfCellIsCurrent( EditableCell targetCell, CellEditorDisplayConditions conditions )
    {
      return ( this.Match( conditions, CellEditorDisplayConditions.CellIsCurrent ) )
          && ( this.IsCurrentCell( targetCell ) );
    }

    private bool DisplayEditorIfRowIsCurrent( EditableCell targetCell, CellEditorDisplayConditions conditions )
    {
      return ( this.Match( conditions, CellEditorDisplayConditions.RowIsCurrent ) )
          && ( this.IsCurrentRow( targetCell ) );
    }

    private bool DisplayEditorIfRowIsBeingEdited( EditableCell targetCell, CellEditorDisplayConditions conditions )
    {
      return ( this.Match( conditions, CellEditorDisplayConditions.RowIsBeingEdited ) )
          && ( this.IsCurrentRow( targetCell ) );
    }

    private bool DisplayEditorIfMouseIsOverCell( EditableCell targetCell, CellEditorDisplayConditions conditions )
    {
      return ( this.Match( conditions, CellEditorDisplayConditions.MouseOverCell ) )
          && ( this.IsMouseOverCell( targetCell ) );
    }

    private bool DisplayEditorIfMouseIsOverRow( EditableCell targetCell, CellEditorDisplayConditions conditions )
    {
      return ( this.Match( conditions, CellEditorDisplayConditions.MouseOverRow ) )
          && ( this.IsMouseOverRow( targetCell ) );
    }

    private bool IsCurrentRow( EditableRow targetRow )
    {
      return ( targetRow != null )
          && ( this.IsCurrentRow( targetRow.ViewModel ) );
    }

    private bool IsCurrentRow( EditableCell targetCell )
    {
      return ( targetCell != null )
          && ( this.IsCurrentRow( ( EditableRow )targetCell.ParentRow ) );
    }

    private bool IsCurrentCell( EditableCell targetCell )
    {
      if( targetCell == null )
        return false;

      var parentRow = targetCell.ParentRow as EditableRow;
      if( parentRow == null )
        return false;

      return this.IsCurrentCell( parentRow.ViewModel, targetCell.ViewModel );
    }

    private bool IsCurrentRow( EditableRowViewModel targetRow )
    {
      return ( targetRow != null )
          && ( object.Equals( targetRow.DataPath, this.CurrentDataPath ) );
    }

    private bool IsCurrentCell( EditableRowViewModel targetRow, EditableCellViewModel targetCell )
    {
      if( targetCell == null )
        return false;

      var column = targetCell.Column;
      if( column == null )
        return false;

      return ( column == this.CurrentColumn )
          && ( this.IsCurrentRow( targetRow ) );
    }

    private bool IsMouseOverCell( EditableCell targetCell )
    {
      var column = targetCell.ParentColumn;

      return ( column != null )
          && ( column == this.CurrentMouseOverColumn )
          && ( this.IsMouseOverRow( targetCell ) );
    }

    private bool IsMouseOverRow( EditableCell targetCell )
    {
      var row = targetCell.ParentRow as EditableRow;

      return ( row != null )
          && ( row == this.CurrentMouseOverRow );
    }

    private bool Match( CellEditorDisplayConditions conditions, CellEditorDisplayConditions requirements )
    {
      return ( ( conditions & requirements ) == requirements );
    }

    private bool Match( EditTriggers conditions, EditTriggers requirements )
    {
      return ( ( conditions & requirements ) == requirements );
    }
  }
}
